/************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 * 
 * Use is subject to license terms.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ************************************************************************/

/*
 * This file is automatically generated.
 * Don't edit manually.
 */
package org.odftoolkit.odfdom.pkg;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.DOMException;

/** This factory determines what elements are being used in the DOC layer
 * (ie. the convenient layer).
 *
 *  The mapping of ODF element to convenient class can be changed from the user
 *  during run time.
 *
 *  For example, a user might want to create a table always with a certain style or default data and
 *  might want to overwrite the mapping for <code>{odf.element table:table}</code>, that a different
 *  class instead of <code>OdfTable</code> is being used.
 * 
 */
public class OdfXMLFactory {

	private static Map<OdfName, Class> mElementTypes = new HashMap<OdfName, Class>();
	private static Map<OdfName, Class> mAttributeTypes = new HashMap<OdfName, Class>();
	private static Map<String, String> mElementRenames = new HashMap<String, String>();
	//a set for the element which need to load class from incubator package.
	private static Set<String> mIncubatorElements = new HashSet<String>();
	private static final String LOCAL_NAME_DELIMITER = "-";
	private static final String ELEMENT_NAME_DELIMITER = ":";
	private static final String ELEMENT_PACKAGE_NAME = "element";
	private static final String ATTRIBUTE_PACKAGE_NAME = "attribute";

	static {
		mElementRenames.put("text:h", "text:heading");
		mElementRenames.put("text:p", "text:paragraph");

		mIncubatorElements.add("draw:frame");
		mIncubatorElements.add("draw:image");
		mIncubatorElements.add("number:currency-style");
		mIncubatorElements.add("number:date-style");
		mIncubatorElements.add("number:percentage-style");
		mIncubatorElements.add("number:number-style");
		mIncubatorElements.add("number:time-style");
		mIncubatorElements.add("office:automatic-styles");
		mIncubatorElements.add("office:master-styles");
		mIncubatorElements.add("office:styles");
		mIncubatorElements.add("style:default-style");
		mIncubatorElements.add("style:style");
		mIncubatorElements.add("style:page-layout");
		mIncubatorElements.add("text:h");
		mIncubatorElements.add("text:list");
		mIncubatorElements.add("text:list-level-style-bullet");
		mIncubatorElements.add("text:list-level-style-image");
		mIncubatorElements.add("text:list-level-style-number");
		mIncubatorElements.add("text:list-style");
		mIncubatorElements.add("text:outline-level-style");
		mIncubatorElements.add("text:outline-style");
		mIncubatorElements.add("text:p");
		mIncubatorElements.add("text:span");
	}

	/** Mapping an ODF element to a new Java DOM element class.
	 *  Note: There is a default class for each element being generated from the latest ODF schema 
	 */
	private static void setOdfElementClass(OdfName odfName, Class className) {
		mElementTypes.put(odfName, className);
	}

	/** Mapping an ODF attribute to a new Java DOM attribute class.
	 *  Note: There is a default class for each element being generated from the latest ODF schema. */
	private static void setOdfAttributeClass(OdfName odfName, Class className) {
		mAttributeTypes.put(odfName, className);
	}

	/**
	 * @param odfName the name of the ODF attribute the desired DOM class should represent.
	 * @return the Java DOM attribute class to be mapped to a certain ODF attribute. */
	private static Class getOdfAttributeClass(OdfName odfName) {
		return getOdfNodeClass(odfName, ATTRIBUTE_PACKAGE_NAME, mAttributeTypes);
	}

	/**
	 * @param odfName the name of the ODF element the desired DOM class should represent.
	 * @return the Java DOM element class to be mapped to a certain ODF element. */
	private static Class getOdfElementClass(OdfName odfName) {
		return getOdfNodeClass(odfName, ELEMENT_PACKAGE_NAME, mElementTypes);
	}

	private static Class getOdfNodeClass(OdfName odfName, String nodeType, Map<OdfName, Class> classCache) {
		Class c = null;
		String className = "";
		c = classCache.get(odfName);
		if (c == null) {
			String prefix = odfName.getPrefix();
			if (prefix != null && !(nodeType.equals(ATTRIBUTE_PACKAGE_NAME) && prefix.equals("xmlns"))) {
				String qName = odfName.getQName();
				String localName = odfName.getLocalName();
				//judge whether the element need to load class from incubator package.
				if (mIncubatorElements.contains(qName)) {
					//judge whether the element need to rename before find class name.
					if (mElementRenames.containsKey(qName)) {
						String renameName = mElementRenames.get(qName);
						StringTokenizer stok = new StringTokenizer(renameName, ELEMENT_NAME_DELIMITER);
						prefix = stok.nextToken();
						localName = stok.nextToken();
					}
					className = getOdfIncubatorNodeClassName(prefix, localName);
				} else if ("manifest".equals(prefix)) {
					className = getOdfPKGNodeClassName(prefix, localName, nodeType);
				} else {
					className = getOdfDOMNodeClassName(prefix, localName, nodeType);
				}
				try {
					c = Class.forName(className);
					classCache.put(odfName, c);
				} catch (ClassNotFoundException ex) {
					// all classes are first tring to load and warning is given later
				} catch (NoClassDefFoundError dex) {
					Logger.getLogger(OdfXMLFactory.class.getName()).log(Level.INFO, "NoClassDefFoundError: " + className, dex.getMessage());
				}
			}
		}
		return c;
	}

	private static String getOdfIncubatorNodeClassName(String prefix, String localName) {
		boolean contains = false;
		StringBuilder className = new StringBuilder();

		if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
			StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
			while (stok.hasMoreElements()) {
				String substr = stok.nextToken();
				if (substr.equals(prefix)) {
					contains = true;
				}
				className = className.append(toUpperCaseFirstCharacter(substr));
			}
		} else {
			className = className.append(toUpperCaseFirstCharacter(localName));
		}
		if (!((contains && !localName.endsWith("table"))
				|| (localName.equals(prefix))
				|| (localName.startsWith(prefix) && prefix.equals("anim")))) {
			className = className.insert(0, toUpperCaseFirstCharacter(prefix));
		}
		className = className.insert(0, "org.odftoolkit.odfdom.incubator.doc." + prefix + "." + "Odf");

		return className.toString();
	}
	
	private static String getOdfPKGNodeClassName(String prefix, String localName, String nodeType) {
		StringBuilder className = new StringBuilder("org.odftoolkit.odfdom.pkg." + prefix + ".");
		if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
			StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
			while (stok.hasMoreElements()) {
				className = className.append(toUpperCaseFirstCharacter(stok.nextToken()));
			}
		} else {
			className = className.append(toUpperCaseFirstCharacter(localName));
		}
		className.append(toUpperCaseFirstCharacter(nodeType));
		return className.toString();
	}
	
	private static String getOdfDOMNodeClassName(String prefix, String localName, String nodeType) {
		StringBuilder className = new StringBuilder("org.odftoolkit.odfdom.dom." + nodeType + "." + prefix + ".");
		className = className.append(toUpperCaseFirstCharacter(prefix));
		if (localName.indexOf(LOCAL_NAME_DELIMITER) != -1) {
			StringTokenizer stok = new StringTokenizer(localName, LOCAL_NAME_DELIMITER);
			while (stok.hasMoreElements()) {
				className = className.append(toUpperCaseFirstCharacter(stok.nextToken()));
			}
		} else {
			className = className.append(toUpperCaseFirstCharacter(localName));
		}
		className.append(toUpperCaseFirstCharacter(nodeType));
		return className.toString();
	}

	private static String toUpperCaseFirstCharacter(String token) {
		return token.substring(0, 1).toUpperCase() + token.substring(1);
	}

	public static OdfElement newOdfElement(OdfFileDom dom, OdfName name) throws DOMException {
		OdfElement element = null;

		// lookup registered element class for qname
		Class elementClass = getOdfElementClass(name);

		// if a class was registered create an instance of that class
		if (elementClass != null) {
			element = (OdfElement) getNodeFromClass(dom, elementClass);
		} else {
			String oldPrefix = name.getPrefix();
			if (oldPrefix != null) {
				// check if the namespace prefix is correct or add potential namespace to DOM
				OdfName adaptedName = addNamespaceToDom(name, dom);
				String newPrefix = adaptedName.getPrefix();
				// in case the prefix was changed as it existed before
				if (oldPrefix != null && !oldPrefix.equals(newPrefix)
						// "_1" is the suffix added by OdfFileDom to an existing Namespace
						&& newPrefix.indexOf("__") == -1) {
					// look up again if there is a class registered for this prefix
					element = newOdfElement(dom, adaptedName);
				} else {
					element = (OdfElement) new OdfAlienElement(dom, adaptedName);
					Logger.getLogger(OdfXMLFactory.class.getName()).log(Level.FINE, "None-ODF element created for {0}", adaptedName.getQName());
				}
			} else {
				element = (OdfElement) new OdfAlienElement(dom, name);
				Logger.getLogger(OdfXMLFactory.class.getName()).log(Level.FINE, "None-ODF element created for {0}", name.getQName());
			}
		}
		return element;
	}

	public static OdfAttribute newOdfAttribute(OdfFileDom dom, OdfName name) throws DOMException {
		OdfAttribute attr = null;

		// lookup registered attribute class for qname
		Class attributeClass = getOdfAttributeClass(name);

		// if a class was registered create an instance of that class
		if (attributeClass != null) {
			attr = (OdfAttribute) getNodeFromClass(dom, attributeClass);
		} else { // in case it is not a default ODF
			// add a namespace unless it is a xmlns attribute (no attr value to set the uri)
			String prefix = name.getPrefix();
			if (prefix != null && !prefix.equals("xmlns")) {
				// check if the namespace prefix is correct or add potential namespace to DOM
				OdfName adaptedName = addNamespaceToDom(name, dom);
				String newPrefix = adaptedName.getPrefix();
				// in case the prefix was changed as it existed before
				if (!prefix.equals(newPrefix) && newPrefix.indexOf("__") == -1) {
					// look up again if there is a class registered for this prefix
					attr = newOdfAttribute(dom, adaptedName);
				} else {
					attr = (OdfAttribute) new OdfAlienAttribute(dom, name);
					Logger.getLogger(OdfXMLFactory.class.getName()).log(Level.FINE, "None-ODF attribute created for {0}", adaptedName.getQName());
				}
			} else {
				// create an alien attribute for namespace attribute "xmlns:*"
				attr = (OdfAttribute) new OdfAlienAttribute(dom, name);
			}

		}
		return attr;
	}

	private static OdfName addNamespaceToDom(OdfName name, OdfFileDom dom) {
		OdfNamespace newNS = dom.setNamespace(name.getPrefix(), name.getUri());
		return OdfName.newName(newNS, name.getLocalName());
	}

	/**
	 * @param dom the XML DOM file where the node should be created on.
	 * @param nodeClass being an XMLNode the Java class of the instance to be created.
	 * @return an object instance of the XML node class being provided (usally an attribute or element). */
	static Object getNodeFromClass(OdfFileDom dom, Class nodeClass) {
		Object o = null;
		try {
			Constructor ctor = nodeClass.getConstructor(new Class[]{OdfFileDom.class});
			o = ctor.newInstance(new Object[]{dom});
		} catch (Exception cause) {
			// an exception at this point is a bug. Throw an Error
			throw new Error("ODF DOM error in attribute factory", cause);
		}
		return o;
	}
}
